package com.blank.shop.ssr.controller;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.blank.shop.ssr.entity.Product;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 淘宝聚划算功能实现+防止缓存击穿
 * <p>
 * 100%高并发，绝对不可以用mysql实现
 * 先把mysql里面参加活动的数据抽取进redis，一般采用定时器扫描来决定上线活动还是下线取消。
 * 支持分页功能，一页20条记录
 * <p>
 * 最终选择list数据类型
 * <p>
 * <br/>Date 2021/9/7
 * <br/>Time 16:59:21
 *
 * @author _blank
 */
@Slf4j
@RestController
public class JhsProductController implements InitializingBean {

    public static final String JHS_KEY = "jhs";

    public static final String JHS_KEY_A = "jhs:a";

    public static final String JHS_KEY_B = "jhs:b";

    private final Snowflake snowflake = IdUtil.getSnowflake();

    private ListOperations<String, Product> opsForList;

    @Resource
    private RedisTemplate<String, Product> redisTemplate;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.opsForList = this.redisTemplate.opsForList();
    }

    /**
     * 模拟定时器，定时把数据库的特价商品，刷新到redis中
     * <p>
     * QPS上1000后导致可怕的缓存击穿
     * QPS上1000后导致可怕的缓存击穿
     * QPS上1000后导致可怕的缓存击穿
     */
    @Scheduled(initialDelay = 5 * 1000L, fixedDelay = 60 * 1000L)
    public void initProducts() {
        final List<Product> list = Lists.newArrayListWithExpectedSize(20);
        // 模拟从数据库读取100件特价商品，用于加载到聚划算的页面中
        for (int i = 20; i > 0; i--) {
            final Product product = new Product(snowflake.nextId()
                    , RandomUtil.randomString(6)
                    , RandomUtil.randomInt(1000)
                    , RandomUtil.randomString(9)
            );
            list.add(product);
        }

        // ////////////////////////////////////////////////// 非原子操作
        // 采用redis list数据结构的lpush来实现存储
        this.redisTemplate.delete(JHS_KEY);
        // lpush命令
        this.opsForList.leftPushAll(JHS_KEY, list);
        // //////////////////////////////////////////////////

        // --------------------------------------------------------------------------- 优化方式
        //先更新B缓存
        this.redisTemplate.delete(JHS_KEY_B);
        this.opsForList.leftPushAll(JHS_KEY_B, list);
        this.redisTemplate.expire(JHS_KEY_B, 20L, TimeUnit.DAYS);
        //再更新A缓存
        this.redisTemplate.delete(JHS_KEY_A);
        this.opsForList.leftPushAll(JHS_KEY_A, list);
        this.redisTemplate.expire(JHS_KEY_A, 15L, TimeUnit.DAYS);
        // ---------------------------------------------------------------------------

        log.info("runJhs定时刷新..............");
    }

    /**
     * 按照分页和每页显示容量，点击查看
     * 分页查询：在高并发的情况下，只能走redis查询，走db的话必定会把db打垮
     */
    @GetMapping(path = {"/product/find"})
    public List<Product> find(Long page, Long size) {
        Assert.notNull(page, "page不能为空！");
        Assert.notNull(size, "size不能为空！");

        List<Product> list = null;
        final long start = (page - 1) * size;
        final long end = start + size - 1;
        try {
            //采用redis list数据结构的lrange命令实现分页查询
            list = this.opsForList.range(JHS_KEY, start, end);
            if (CollectionUtils.isEmpty(list)) {
                //TODO 走DB查询
            }
            log.info("查询结果：{}", list);
        } catch (Exception ex) {
            //这里的异常，一般是redis瘫痪 ，或 redis网络timeout
            log.error("exception:", ex);
            //TODO 走DB查询
        }
        return list;
    }

    /**
     * 按照分页和每页显示容量，点击查看AB
     */
    @GetMapping(path = {"/product/find-ab"})
    public List<Product> findAb(Long page, Long size) {
        Assert.notNull(page, "page不能为空！");
        Assert.notNull(size, "size不能为空！");

        List<Product> list = null;
        final long start = (page - 1) * size;
        final long end = start + size - 1;
        try {
            //采用redis list数据结构的lrange命令实现分页查询
            list = this.opsForList.range(JHS_KEY_A, start, end);
            if (CollectionUtils.isEmpty(list)) {
                log.info("=========A缓存已经失效了，记得人工修补，B缓存自动延续5天");
                //用户先查询缓存A(上面的代码)，如果缓存A查询不到（例如，更新缓存的时候删除了），再查询缓存B
                this.opsForList.range(JHS_KEY_B, start, end);
            }
            log.info("查询结果：{}", list);
        } catch (Exception ex) {
            //这里的异常，一般是redis瘫痪 ，或 redis网络timeout
            log.error("exception:", ex);
            //TODO 走DB查询
        }
        return list;
    }

}
